#include "myconf.h"

#include <stdio.h>
#include <stdarg.h>

/*******************************************
 * Native Tokyo Tyrant Object Access Functions
 ********************************************/
static jfieldID rdb_fid_ptr;
static jfieldID qry_fid_ptr;

void init_native_rdb_field
  (JNIEnv *env, jclass cls) {
	rdb_fid_ptr = (*env)->GetFieldID(env, cls, "ptr", "J");
	if (!rdb_fid_ptr) {
		throwillsta(env, "get field id failed: ptr");
	}
}

void set_native_rdb
  (JNIEnv *env, jobject self, TCRDB *rdb) {
	(*env)->SetLongField(env, self, rdb_fid_ptr, (intptr_t)rdb);
}

TCRDB *native_rdb(JNIEnv *env, jobject self) {
	return (TCRDB *)(intptr_t)(*env)->GetLongField(env, self, rdb_fid_ptr);
}


void init_native_qry_field
  (JNIEnv *env, jclass cls) {
	qry_fid_ptr = (*env)->GetFieldID(env, cls, "ptr", "J");
	if (!qry_fid_ptr) {
		throwillsta(env, "get field id failed: ptr");
	}
}

void set_native_qry
  (JNIEnv *env, jobject self, RDBQRY *qry) {
	(*env)->SetLongField(env, self, qry_fid_ptr, (intptr_t)qry);
}

RDBQRY *native_qry
  (JNIEnv *env, jobject self) {
	return (RDBQRY *)(intptr_t)(*env)->GetLongField(env, self, qry_fid_ptr);
}



/*******************************************
 * Java Exception throw Functions
 ********************************************/
static void throwexception(JNIEnv *env, const char *clsname, const char *message){
	char errmsg[64];
	char exp_message[256];

	if ((*env)->ExceptionCheck(env)) {
		jthrowable jinitexp = (*env)->ExceptionOccurred(env);

		jclass clsinitexp = (*env)->GetObjectClass(env, jinitexp);
		if (!clsinitexp) {
			snprintf(errmsg, sizeof(errmsg), "exception class not found");
			(*env)->FatalError(env, errmsg);
			return;
		}
		
		jstring jclsname = getclsname(env, clsinitexp);
		jboolean ic_clsname;
		const char *clsname = (*env)->GetStringUTFChars(env, jclsname, &ic_clsname);
		const char *mname = "getMessage";
		const char *msig = "()Ljava/lang/String;";
		
		jmethodID mid = (*env)->GetMethodID(env, clsinitexp, mname, msig);
		if (!mid) {
			snprintf(errmsg, sizeof(errmsg), "exception method id[%s %s %s]", clsname, mname, msig);
			(*env)->FatalError(env, errmsg);
			return;
		}
	
		jstring jinitmsg = (*env)->CallObjectMethod(env, jinitexp, mid);
		if (jinitmsg) {
			jboolean ic_initmsg;
			const char *initmsg = (*env)->GetStringUTFChars(env, jinitmsg, &ic_initmsg);
			if (!initmsg) {
				snprintf(errmsg, sizeof(errmsg), "initcause message allocate error: [%s %s %s]", clsname, mname, msig);
				(*env)->FatalError(env, errmsg);
				return;
			}

			snprintf(exp_message, sizeof(exp_message), "%s initCause[%s: %s]", message, clsname, initmsg);

			if (ic_initmsg) (*env)->ReleaseStringUTFChars(env, jinitmsg, initmsg);
		} else {
			snprintf(exp_message, sizeof(exp_message), "%s", message);
		}
		
		if (ic_clsname) (*env)->ReleaseStringUTFChars(env, jclsname, clsname);
	} else {
		snprintf(exp_message, sizeof(exp_message), "%s", message);
	}

	jclass cls = (*env)->FindClass(env, clsname);
	if (!cls) {
		snprintf(errmsg, sizeof(errmsg), "exception class not found: %s", clsname);
		(*env)->FatalError(env, errmsg);
		return;
	}
	(*env)->ThrowNew(env, cls, exp_message);
}


void throwoutmem(JNIEnv *env, const char *msg){
	if (msg) {
		throwexception(env, CLSEOUTMEM, msg);
	} else {
		throwexception(env, CLSEOUTMEM, "out of memory");
	}
}

void throwillarg(JNIEnv *env, const char *msg){
	if (msg) {
		throwexception(env, CLSEILLARG, msg);
	} else {
		throwexception(env, CLSEILLARG, "illegal argument");
	}
}

void throwillsta(JNIEnv *env, const char *msg){
	if (msg) {
		throwexception(env, CLSEILLSTA, msg);
	} else {
		throwexception(env, CLSEILLSTA, "Illegal state");
	}
}

void throwclsnf(JNIEnv *env, const char *msg){
	if (msg) {
		throwexception(env, CLSECLSNF, msg);
	} else {
		throwexception(env, CLSECLSNF, "class not found");
	}
}

/*******************************************
 * Java Class & Instance Operate Functions
 ********************************************/
jstring getclsname(JNIEnv *env, jclass cls) {
	char errmsg[64];
	jclass clscls = (*env)->GetObjectClass(env, cls);

	const char *mname = "getName";
	const char *msig = "()Ljava/lang/String;";
	jmethodID mid = (*env)->GetMethodID(env, clscls, mname, msig);
	if (!mid) {
		snprintf(errmsg, sizeof(errmsg), "get method id[%s %s]", mname, msig);
		(*env)->FatalError(env, errmsg);
		return NULL;
	}

	jstring jclsname = (*env)->CallObjectMethod(env, cls, mid);
	
	return jclsname;
}

jobject new_jobject
  (JNIEnv *env, const char *clsname, const char *csig, ...) {
	char msg[64];
	va_list cargs;
	va_start(cargs, csig);

	jclass cls = (*env)->FindClass(env, clsname);
	if (!cls) {
		snprintf(msg, sizeof(msg), "class not found: %s", clsname);
		throwclsnf(env, msg);
		return NULL;
	}
	
	const char *cname = "<init>";
	jmethodID mid = (*env)->GetMethodID(env, cls, cname, csig);
	if (!mid) {
		snprintf(msg, sizeof(msg), "get method id[%s %s %s]", clsname, cname, csig);
		throwillarg(env, msg);
		return NULL;
	}
	
	jobject jobj = (*env)->NewObjectV(env, cls, mid, cargs);
	if (!jobj) {
		snprintf(msg, sizeof(msg), "new object fail[%s %s %s]", clsname, cname, csig);
		throwoutmem(env, msg);
		return NULL;
	}

	va_end(cargs);
	return jobj;
}


jobject invoke_jobject_method
  (JNIEnv *env, jobject jobj, const char *mname, const char *msig, ...) {
	char msg[64];
	va_list margs;
	va_start(margs, msig);
	
  	jclass cls = (*env)->GetObjectClass(env, jobj);
  	if (!cls) {
  		throwoutmem(env, "get object class");
  		return NULL;
  	}

	jmethodID mid = (*env)->GetMethodID(env, cls, mname, msig);
	if (!mid) {
		snprintf(msg, sizeof(msg), "get method id[%s %s]", mname, msig);
		throwillarg(env, msg);
		return NULL;
	}
	
	jobject ret = (*env)->CallObjectMethodV(env, jobj, mid, margs);
	
	va_end(margs);
	return ret;
}

jint invoke_jint_method
  (JNIEnv *env, jobject jobj, const char *mname, const char *msig, ...) {
	char msg[64];
	va_list margs;
	va_start(margs, msig);
	
  	jclass cls = (*env)->GetObjectClass(env, jobj);
  	if (!cls) {
  		throwoutmem(env, "get object class");
  		return 0;
  	}

	jmethodID mid = (*env)->GetMethodID(env, cls, mname, msig);
	if (!mid) {
		snprintf(msg, sizeof(msg), "get method id[%s %s]", mname, msig);
		throwillarg(env, msg);
		return 0;
	}
	
	jint ret = (*env)->CallIntMethodV(env, jobj, mid, margs);
	
	va_end(margs);
	return ret;
}

jboolean invoke_jboolean_method
  (JNIEnv *env, jobject jobj, const char *mname, const char *msig, ...) {
	char msg[64];
	va_list margs;
	va_start(margs, msig);
	
  	jclass cls = (*env)->GetObjectClass(env, jobj);
  	if (!cls) {
  		throwoutmem(env, "get object class");
  		return false;
  	}

	jmethodID mid = (*env)->GetMethodID(env, cls, mname, msig);
	if (!mid) {
		snprintf(msg, sizeof(msg), "get method id[%s %s]", mname, msig);
		throwillarg(env, msg);
		return false;
	}
	
	jboolean ret = (*env)->CallBooleanMethodV(env, jobj, mid, margs);
	
	va_end(margs);
	return ret;
}

/*******************************************
 * Java Map & List Convert Functions
 ********************************************/
static jobjectArray get_map_keys
  (JNIEnv *env, jobject jbytemap) {
  
	jobject jbyteSet = invoke_jobject_method(env, jbytemap, "keySet", "()Ljava/util/Set;");
	if (!jbyteSet) {
		throwillsta(env, "call map keySet");
		return NULL;
	}
	jobjectArray jbytekeys = invoke_jobject_method(env, jbyteSet, "toArray", "()[Ljava/lang/Object;");
	if (!jbytekeys) {
		throwillsta(env, "call to array");
		return NULL;
	}

	return jbytekeys;
}

TCMAP *jbytemap2tcmap
  (JNIEnv *env, jobject jbytemap/*Map<byte[], byte[]>*/) {
  	if (!jbytemap) {
  		throwillarg(env, "jbytemap is null");
  		return NULL;
  	}
  
	jobjectArray jbytekeys = get_map_keys(env, jbytemap);
  	if (!jbytekeys) {
  		throwoutmem(env, "jbytemap get keys");
  		return NULL;
  	}
  	jsize keyslen = (*env)->GetArrayLength(env, jbytekeys);
  	
  	TCMAP *tcmap = tcmapnew2(keyslen);
  	if (!tcmap) {
  		throwoutmem(env, "tcmapnew2 faile");
  		return NULL;
  	}
	
	jbyteArray jbytekey, jbytevalue;
	jboolean ic_key, ic_value;
	jsize keysiz, valuesiz;
	jbyte *keybuf, *valuebuf;
	for (int i = 0; i < keyslen; i++) {
		jbytekey = (*env)->GetObjectArrayElement(env, jbytekeys, i);
		jbytevalue = invoke_jobject_method(env, jbytemap, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", jbytekey);
		if (!jbytevalue) {
			throwillarg(env, "map value(jbytevalue) is null");
			return NULL;
		}

	  	keysiz = (*env)->GetArrayLength(env, jbytekey);
	  	keybuf = (*env)->GetByteArrayElements(env, jbytekey, &ic_key);
	  	if (!keybuf) {
	  		throwoutmem(env, "jbytekey to jbyte*");
	  		return NULL;
	  	}
	  	
	  	valuesiz = (*env)->GetArrayLength(env, jbytevalue);
	  	valuebuf = (*env)->GetByteArrayElements(env, jbytevalue, &ic_value);
	  	if (!valuebuf) {
	  		throwoutmem(env, "jbytevalue to jbyte*");
	  		return NULL;
	  	}
	  	
	  	tcmapput(tcmap, keybuf, keysiz, valuebuf, valuesiz);
		
	  	if (ic_key) (*env)->ReleaseByteArrayElements(env, jbytekey, keybuf, JNI_ABORT);
	  	if (ic_value) (*env)->ReleaseByteArrayElements(env, jbytevalue, valuebuf, JNI_ABORT);
	}

	return tcmap;
}

bool puttcmap2jbytemap
  (TCMAP *tcmap, JNIEnv *env, jobject jbytemap) {
  	if (!tcmap || !jbytemap) {
  		throwillarg(env, "tcmap or jbytemap is null");
  		return false;
  	}
  
	jobjectArray jbytekeys = get_map_keys(env, jbytemap);
  	if (!jbytekeys) {
  		throwoutmem(env, "jbytemap get keys");
  		return false;
  	}
  	jsize keyslen = (*env)->GetArrayLength(env, jbytekeys);
	
	jbyteArray jbytekey, jbytevalue;
	jboolean ic_key;
	jsize keysiz, valuesiz;
	jbyte *keybuf;
	const jbyte *valuebuf;
	for (int i = 0; i < keyslen; i++) {
		jbytekey = (*env)->GetObjectArrayElement(env, jbytekeys, i);

		keysiz = (*env)->GetArrayLength(env, jbytekey);
		keybuf = (*env)->GetByteArrayElements(env, jbytekey, &ic_key);
		if (!keybuf) {
			throwoutmem(env, "jbytekey to jbyte*");
			return false;
		}
		
		// tcmap に値があれば　jbytemapに設定　なければ、jmap のキーを削除
		valuebuf = tcmapget(tcmap, keybuf, keysiz, &valuesiz);
		if (valuebuf) {
			jbytevalue = (*env)->NewByteArray(env, valuesiz);
			if (!jbytevalue) {
				throwoutmem(env, "jbyte array new");
				return false;
			}
			(*env)->SetByteArrayRegion(env, jbytevalue, 0, valuesiz, valuebuf);
			
			invoke_jobject_method(env, jbytemap, "put", 
			    "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", jbytekey, jbytevalue);
		} else {
			invoke_jobject_method(env, jbytemap, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", jbytekey);
		}

	  	if (ic_key) (*env)->ReleaseByteArrayElements(env, jbytekey, keybuf, JNI_ABORT);
	}
  	
	return true; 
}

jobject tclist2jbytelist
  (JNIEnv *env, const TCLIST *tclist) {
	if (!tclist) {
		throwillarg(env, "tclist is null");
		return NULL;
	}

	int rnum = tclistnum(tclist);
	jobject jlist = new_jobject(env, "java/util/ArrayList", "(I)V", rnum);
	if (!jlist) {
		throwoutmem(env, "jlist new error");
		return NULL;
	}
	
	int valuesiz;
	const jbyte *value;
	jbyteArray jvalue;
	for (int i = 0; i < rnum; i++) {
		value = tclistval(tclist, i, &valuesiz);
		if (value) {
			jvalue = (*env)->NewByteArray(env, valuesiz);
			if (!jvalue) {
				throwoutmem(env, "jbyteArray allocate");
				return NULL;
			}
			(*env)->SetByteArrayRegion(env, jvalue, 0, valuesiz, value);
			invoke_jboolean_method(env, jlist, "add", "(Ljava/lang/Object;)Z", jvalue);
		}
	}
	
	return jlist;
}

jobject tclist2jstrlist
  (JNIEnv *env, const TCLIST *tclist) {
	if (!tclist) {
		throwillarg(env, "tclist is null");
		return NULL;
	}
	int rnum = tclistnum(tclist);
	jobject jlist = new_jobject(env, "java/util/ArrayList", "(I)V", rnum);
	if (!jlist) {
		throwoutmem(env, "jlist new error");
		return NULL;
	}
	
	const char *value;
	jstring jvalue;
	for (int i = 0; i < rnum; i++) {
		value = tclistval2(tclist, i);
		if (value) {
			jvalue = (*env)->NewStringUTF(env, value);
			if (!jvalue) {
				throwoutmem(env, "jstring allocate");
				return NULL;
			}

			invoke_jboolean_method(env, jlist, "add", "(Ljava/lang/Object;)Z", jvalue);
		}
	}
	
	return jlist;
}

TCLIST *jbytearr2tclist
  (JNIEnv *env, jobjectArray jbytesarr /* byte[][] */) {
	int len = (*env)->GetArrayLength(env, jbytesarr);

	TCLIST *list = tclistnew2(len);
	if (!list) {
		throwoutmem(env, "tclistnew2 faile");
		return NULL;
	}

	jbyteArray jbytes;
	jboolean ic_bytes;
	jsize bytessiz;
	jbyte *bytesbuf;
	for (int i = 0; i < len; i++) {
		jbytes = (*env)->GetObjectArrayElement(env, jbytesarr, i);
		bytessiz = (*env)->GetArrayLength(env, jbytes);
		bytesbuf = (*env)->GetByteArrayElements(env, jbytes, &ic_bytes);
		if (!bytesbuf) {
			throwoutmem(env, "jbytearr elements to jbyte");
			return NULL;
		}
		
		tclistpush(list, bytesbuf, bytessiz);
		
		if (ic_bytes) (*env)->ReleaseByteArrayElements(env, jbytes, bytesbuf, JNI_ABORT);
		ic_bytes = false;
		bytesbuf = NULL;
	}

	return list;
}

TCLIST *jstrarr2tclist
  (JNIEnv *env, jobjectArray jstrarr /* String[] */) {
	int len = (*env)->GetArrayLength(env, jstrarr);

	TCLIST *list = tclistnew2(len);
	if (!list) {
		throwoutmem(env, "tclistnew2 faile");
		return NULL;
	}
	
	jstring jstr;
	jboolean ic_str;
	const char *str;
	for (int i = 0; i < len; i++) {
		jstr = (*env)->GetObjectArrayElement(env, jstrarr, i);
		str = (*env)->GetStringUTFChars(env, jstr, &ic_str);
		if (!str) {
			throwoutmem(env, "jstrarr elements to char+");
			return NULL;
		}
		
		tclistpush2(list, str);
		
		if (ic_str) (*env)->ReleaseStringUTFChars(env, jstr, str);
		ic_str = false;
		str = NULL;
	}

	return list;
}

jobject tcmap2jmap
  (TCMAP *map, JNIEnv *env, int jmapktype, int jmapvtype) {
	if (jmapktype != JTTMAPTYPESTRG
	  && jmapktype != JTTMAPTYPEBYTS) {
		throwillarg(env, "jmapktype is JTTMAPTYPESTRG or JTTMAPTYPEBYTS");
		return NULL;
	}
	if (jmapvtype != JTTMAPTYPESTRG
	  && jmapvtype != JTTMAPTYPEBYTS) {
		throwillarg(env, "jmapvtype is JTTMAPTYPESTRG or JTTMAPTYPEBYTS");
		return NULL;
	}

	int mapsiz = tcmaprnum(map);
	jobject jmap = new_jobject(env, "java/util/HashMap", "(I)V", mapsiz);
	if (!jmap) {
		throwoutmem(env, "jmap new error");
		return NULL;
	}
	
	tcmapiterinit(map);
	
	jobject jkey, jvalue;
	int keysiz, valuesiz;
	const void *keybuf ,*valuebuf;

	while ((keybuf = tcmapiternext(map, &keysiz)) != NULL) {
		if (jmapktype == JTTMAPTYPEBYTS) {
			jkey = (*env)->NewByteArray(env, keysiz);
			if (!jkey) {
				throwoutmem(env, "jbyte array allocate error");
				return NULL;
			}
			(*env)->SetByteArrayRegion(env, jkey, 0, keysiz, keybuf);
		} else {
			jkey = (*env)->NewStringUTF(env, keybuf);
			if (!jkey) {
				throwoutmem(env, "jstring allocate error");
				return NULL;
			}
		}
		
		valuebuf = tcmapget(map, keybuf, keysiz, &valuesiz);
		if (jmapvtype == JTTMAPTYPEBYTS) {
			jvalue = (*env)->NewByteArray(env, valuesiz);
			if (!jvalue) {
				throwoutmem(env, "jbyte array allocate error");
				return NULL;
			}
			(*env)->SetByteArrayRegion(env, jvalue, 0, valuesiz, valuebuf);
		} else{
			jvalue = (*env)->NewStringUTF(env, valuebuf);
			if (!jvalue) {
				throwoutmem(env, "jstring allocate error");
				return NULL;
			}
		}

		invoke_jobject_method(env, jmap, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", jkey, jvalue);
	}
	
	return jmap;
}


